﻿using System;
using System.Xml;
using XmlStudio.DataLayer;
using XmlStudio.Interfaces;
using XmlStudio.Models.Events;
using XmlStudio.Utilities;

namespace XmlStudio.Models.Actions {
    /// <summary>
    /// Action to add an attribute to certain element.
    /// </summary>
    internal sealed class AddAttributeAction : XmlModelAction {
        #region Fields
        #region Private
        /// <summary>
        /// Node where to add the attribute.
        /// </summary>
        private BigXmlNodeInternal node;

        /// <summary>
        /// Name of the attribute to add.
        /// </summary>
        private string name;

        private string prefix;

        /// <summary>
        /// Value of the attribute.
        /// </summary>
        private string value;

        /// <summary>
        /// Namespace uri of the attribute.
        /// </summary>
        private string namespaceURI;

        /// <summary>
        /// Indicates whether attribute is being added with or without the namespace.
        /// </summary>
        private bool withNamespace = false;
        #endregion
        #endregion

        #region Constructors
        #region Public
        /// <summary>
        /// Initializes a new instance of the <see cref="AddAttributeAction"/> class.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <param name="node">Reference to <see cref="BigXmlNode"/> instance.</param>
        /// <param name="name">Name of the attribute to add.</param>
        /// <param name="value">Value to set.</param>
        public AddAttributeAction(IXmlModelWithNotifications model, BigXmlNodeInternal node, string name, string value)
            : base(model) {
            this.node = node;
            this.wasModified = node.Modified;
            this.name = name;
            this.value = value;
            this.withNamespace = false;
            this.address = this.node.Address.DeepClone<int[]>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AddAttributeAction"/>.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <param name="node">Reference to the <see cref="BigXmlNode"/> instance.</param>
        /// <param name="prefix">Prefix of the attribute to add.</param>
        /// <param name="localName">Local name of the attribute to add.</param>
        /// <param name="namespaceURI">Namespace uri of the attribute to add.</param>
        /// <param name="value">Value of the attribute to add.</param>
        public AddAttributeAction(IXmlModelWithNotifications model, BigXmlNodeInternal node, string prefix, string localName, string namespaceURI, string value)
            : base(model) {
            this.node = node;
            this.wasModified = node.Modified;
            this.name = localName;
            this.namespaceURI = string.IsNullOrEmpty(namespaceURI) ? string.Empty : namespaceURI;
            this.value = value;
            this.withNamespace = true;
            this.prefix = prefix;
            if(this.node.Address != null) {
                this.address = this.node.Address.DeepClone();
            }
        }
        #endregion
        #endregion

        #region Methods
        #region Public
        public override string ToString() {
            return "AddAttributeAction";
        }
        #endregion

        #region Protected
        /// <summary>
        /// Executes the action.
        /// </summary>
        protected override void ExecuteCore() {
            var element = this.node.XmlNode as XmlElement;
            if(element != null) {
                if(withNamespace) {
                    var attribute = new BigXmlAttribute(this.prefix, this.name, this.namespaceURI, this.value);
                    this.node.AddAttribute(attribute);

                    XmlAttribute att = element.OwnerDocument.CreateAttribute(this.prefix, this.name, this.namespaceURI);
                    att.Value = this.value;
                    element.SetAttributeNode(att);

                    this.node.SetModified(true);
                    this.model.SetModified(true);
                    this.model.OnAttributeAdded(new AttributeAddedEventArgs<BigXmlNode, IBigXmlAttribute>(this.node, attribute));
                    this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
                } else {
                    var attribute = new BigXmlAttribute(this.prefix, this.name, this.namespaceURI, this.value);
                    this.node.AddAttribute(attribute);

                    element.SetAttribute(this.name, this.value);

                    this.node.SetModified(true);
                    this.model.SetModified(true);
                    this.model.OnAttributeAdded(new AttributeAddedEventArgs<BigXmlNode, IBigXmlAttribute>(this.node, attribute));
                    this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
                }
            } else {
                throw new XmlException("Cannot set attribute on non element!");
            }
        }

        /// <summary>
        /// Executes the undo for this action.
        /// </summary>
        protected override void UnExecuteCore() {
            var element = this.node.XmlNode as XmlElement;
            if(element != null) {
                if(withNamespace) {
                    IBigXmlAttribute attribute;
                    //string attributeName = string.Format("{0}:{1}", this.name, this.namespaceURI);
                    string attributeName = string.Format("{0}:{1}:{2}", this.prefix, this.name, this.namespaceURI);
                    this.node.Attributes.TryGetValue(attributeName, out attribute);
                    if(attribute == null) { throw new InvalidOperationException("Attribute is missing"); }
                    this.node.RemoveAttribute(this.name, this.namespaceURI, this.prefix);

                    element.RemoveAttribute(this.name, this.namespaceURI);

                    this.node.SetModified(this.wasModified);
                    this.model.OnAttributeRemoved(new AttributeRemovedEventArgs<BigXmlNode, IBigXmlAttribute>(this.node, attribute));
                    this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
                } else {
                    IBigXmlAttribute attribute;
                    //string attributeName = string.Format("{0}:{1}", this.name, this.namespaceURI);
                    string attributeName = string.Format("{0}:{1}:{2}", this.prefix, this.name, this.namespaceURI);
                    this.node.Attributes.TryGetValue(attributeName, out attribute);
                    if(attribute == null) { throw new InvalidOperationException("Attribute is missing"); }
                    this.node.RemoveAttribute(this.name, this.namespaceURI, this.prefix);

                    element.RemoveAttribute(this.name);

                    this.node.SetModified(this.wasModified);
                    this.model.OnAttributeRemoved(new AttributeRemovedEventArgs<BigXmlNode, IBigXmlAttribute>(this.node, attribute));
                    this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
                }
            }
        }
        #endregion
        #endregion
    }
}